﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
using Contracts;
using System.Reflection;
using System.Threading;
using ICSharpCode.SharpZipLib.Zip;

namespace WpfAppHost
{
    [Export("AddinManager")]
    public class AddinManager : IDisposable
    {
        public event EventHandler<AddinAvailableEventArgs> AddinAvailable;
        public event EventHandler AddinLoaded;

        private FileSystemWatcher watcher;

        private static object locker = new object();

        public List<Contracts.IWpfService> Addins { get; set; }

        public IEnumerable<Lazy<IWpfService, IWpfServiceMetadata>> AvailableAddins
        {
            get;
            set;
        }

        static AddinManager()
        {
            // While debugging, use the Addins folder in the bin folder of the solution
            if (System.Diagnostics.Debugger.IsAttached)
            {
                AddinsDir =
                    Path.Combine(Environment.CurrentDirectory, "Addins");
            }
            else
            {
                AddinsDir =
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MefUtilRunner\\Addins");
            }

            try
            {
                if (!Directory.Exists(AddinsDir))
                    Directory.CreateDirectory(AddinsDir);
            }
            catch { // Unable to create addins folder.  Should always have permissions here...
            }
        }

        public AddinManager()
        {
        }

        public void LocateAddins()
        {
            CompositionContainer _container; 

            var _catalog = new AggregateCatalog();

            if (_catalog.Catalogs.Count == 0)
            {
                // Check for built-in addins in the main assembly
                _catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

                // Add any DLL's in the addins folder (if it exists)
                if (Directory.Exists(AddinsDir))
                {
                    // Within the main addin folder, look for specific addin folders
                    foreach (string addinDir in Directory.GetDirectories(AddinsDir, "*.util"))
                    {
                        _catalog.Catalogs.Add(new DirectoryCatalog(addinDir, "*.dll"));
                    }
                }

                _container = new CompositionContainer(_catalog);

                AvailableAddins = _container.GetExports<IWpfService, IWpfServiceMetadata>();
                System.Diagnostics.Debug.Assert(AvailableAddins != null);
            }
        }

        public void Init()
        {
            Init_InstallAddins();
            Init_UpdateAddins();
            Init_DisableAddins();
            Init_DeleteAddins();

            LocateAddins();
            
            Addins = new List<IWpfService>();

            foreach ( var addin in AvailableAddins)
            {
                AddOne(addin);
            }

            // Watch for changes to addins folder
            watcher = new FileSystemWatcher(AddinsDir, "*.*");
            watcher.IncludeSubdirectories = true;
            watcher.Created += new FileSystemEventHandler(watcher_Created);
            watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// Cleanup and update existing addins
        /// </summary>
        private void Init_InstallAddins()
        {
            string[] toInstall = Directory.GetFiles(AddinsDir, "*.util");
            foreach (string entry in toInstall)
            {
                // This will ensure it gets picked up by Init_UpdateAddins
                // It enables one to drop a .util file into the folder to be installed
                File.Move(entry, entry + ".update");
            }
        }

        /// <summary>
        /// Cleanup and update existing addins
        /// </summary>
        private void Init_UpdateAddins()
        {
            string[] toInstall = Directory.GetFiles(AddinsDir, "*.update");
            foreach (string entry in toInstall)
            {
                // Strip off the .install to get the existing folder name
                string entryPath = entry.Substring(0, entry.LastIndexOf(".update"));

                if (Directory.Exists(entryPath))
                {
                    string[] files = Directory.GetFiles(entryPath);
                    foreach (string file in files)
                        File.Delete(file);
                }
                else
                {
                    Directory.CreateDirectory(entryPath);
                }

                // Now unpack the new one
                FastZip arc = new FastZip();
                arc.ExtractZip(entry, entryPath, string.Empty);

                // No need to retain the installer file
                File.Delete(entry);
            }
        }

        private void Init_DeleteAddins()
        {
            string[] toRemove = Directory.GetFiles(AddinsDir, "*.remove");
            foreach (string entry in toRemove)
            {
                // Strip off the .install to get the existing folder name
                string entryPath = entry.Substring(0, entry.IndexOf(".remove"));

                if (Directory.Exists(entryPath))
                {
                    Directory.Delete(entryPath, true);
                }

                // No need to retain the .remove file
                File.Delete(entry);
            }
        }

        private void Init_DisableAddins()
        {
            string[] toDisable = Directory.GetFiles(AddinsDir, "*.disable");
            foreach (string entry in toDisable)
            {
                // Strip off the .install to get the existing folder name
                string currentPath = entry.Substring(0, entry.IndexOf(".disable"));

                if (Directory.Exists(currentPath))
                {
                    Directory.Move(currentPath, currentPath + ".disabled");
                    File.Delete(entry);
                }
            }
        }

        /// <summary>
        /// Handles events raised by new addins appearing in the folder
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void watcher_Created(object sender, FileSystemEventArgs e)
        {
            int lastDot = e.FullPath.LastIndexOf(".");
            string ext = "";
            if (lastDot > -1) ext = e.FullPath.Substring(lastDot + 1);

            if ("util;update".Contains(ext.ToLower()))
            {
                if (AddinAvailable != null)
                    AddinAvailable(this, new AddinAvailableEventArgs(e.FullPath));
            }
        }

        public static string AddinsDir
        {
            get;
            set;
        }

        public T GetServiceByType<T>()
        {
            System.Diagnostics.Debug.Assert(Addins != null);
            return Addins.OfType<T>().FirstOrDefault();
        }


        /// <summary>
        /// Given the path to an addin package (.util), verify that a valid addin exists to install
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public bool VerifyAddinPackage(string path)
        {
            return true;
        }

        public void InstallAddin(string path)
        {
            // Copy new addin to deploy folder
            string fn = Path.GetFileName(path); //i.e. MyAddin.util
            string addinPath = Path.Combine(AddinsDir, fn); //i.e. c:\users\user1\AppData\MefUtilRunner\Addins\MyAddin.util

            // Check for existing addin version
            if (!Directory.Exists(addinPath))
            {
                // New addin - Create deploy folder
                Directory.CreateDirectory(addinPath);
                FastZip arc = new FastZip();
                arc.ExtractZip(path, addinPath, string.Empty);
            }
            else
            {
                // Update addin - Mark to delete on next run
                File.Copy(path, addinPath + ".update");
            }

            AddinAvailable(this, new AddinAvailableEventArgs(path));
        }

        private bool AddOne(Lazy<IWpfService, IWpfServiceMetadata> addin)
        {
            try
            {
                IWpfService newAddin = addin.Value;

                Addins.Add(newAddin);
                if (AddinLoaded != null) AddinLoaded(this, null);

                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Can't really enable since installed addins have no .util file
        /// </summary>
        /// <param name="addin"></param>
        public void EnableAddin(string path)
        {
            string entryPath = path.Substring(0, path.IndexOf(".disabled"));

            if (!Directory.Exists(entryPath))
            {
                Directory.Move(path, entryPath);
            }
        }

        /// <summary>
        /// Creates a dummy 
        /// </summary>
        /// <param name="addin"></param>
        public void DisableAddin(IWpfService addin)
        {
            // Find the name of the actual addin directory
            string addinDir = Path.GetDirectoryName(addin.GetType().Assembly.CodeBase);

            // Flag it so on next run it's not loaded (create dummy file with .disable)
            File.Create(addinDir + ".disable");
        }

        public void UninstallAddin(IWpfService addin)
        {
            // Find the name of the actual addin directory
            string addinDir = Path.GetDirectoryName(addin.GetType().Assembly.Location);

            // Flag it so on next run it's deleted before loading takes place (create dummy file with .remove)
            File.Create(addinDir + ".remove").Close();
        }

        public void CheckForAllUpdates()
        {
            foreach (IWpfService svc in Addins)
            {
                CheckForUpdate(svc);
            }
        }

        public void CheckForUpdate(IWpfService addin)
        {
            // Goes to update URI and checks for newer version somehow...
        }

        public void InstallNewestUpdate(IWpfService addin)
        {
            // Download newest update for addin, copy to set-aside area for loading on next run
        }

        #region IDisposable Members

        public void Dispose()
        {
            // Try to stop each addin
            Addins.ForEach(a => { try { a.Stop(); } catch { ;} });
        }

        #endregion
    }

    public class AddinAvailableEventArgs : EventArgs
    {
        public AddinAvailableEventArgs(string AddinPath)
        {
            this.AddinPath = AddinPath;
        }

        public string AddinPath { get; private set; }
    }
}
